<?php
/**
 * Gitee Webhook自动部署脚本 - 增强版
 * 支持根据目标目录变化判断是否执行部署
 */

// ==================== 配置区 ====================
define('WEBSITE_ROOT', '/alidata1/www/web/webhook.chaihongjun.me'); //服务器部署的网站根目录
define('GIT_REMOTE', 'https://gitee.com/chaihongjun/webhook-test.git'); //Gitee WebHook 通知URL
define('SECRET_KEY', 'chj441315628'); // WebHook 密码
define('TARGET_DIR', 'dist'); //要部署的子目录，留空则部署整个仓库
define('BRANCH', 'master'); //同步的仓库分支
define('GIT_PATH', '/usr/bin/git/bin/git'); //服务器git 路径
define('CHECK_TARGET_CHANGES', true); // 是否检查目标目录变化，true-只有目标目录变化才部署，false-任何变化都部署
define('PROTECTED_DIRS', ['deploy', '.temp_deploy']); // 需要保护的目录列表

// ==================== 函数定义 ====================

/**
 * 日志记录函数
 */
function logMessage($message) {
    $log_file = __DIR__ . '/webhook.log';
    file_put_contents(
        $log_file,
        '[' . date('Y-m-d H:i:s') . '] ' . $message . PHP_EOL,
        FILE_APPEND | LOCK_EX
    );
}

/**
 * 获取请求头信息
 */
function getHeaders() {
    if (function_exists('getallheaders')) {
        return getallheaders();
    }

    $headers = [];
    foreach ($_SERVER as $name => $value) {
        if (substr($name, 0, 5) == 'HTTP_') {
            $header_name = str_replace(
                ' ',
                '-',
                ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))
            );
            $headers[$header_name] = $value;
        }
    }
    return $headers;
}

/**
 * 验证请求合法性
 */
function validateRequest($headers) {
    // 检查请求方法
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        header('HTTP/1.1 405 Method Not Allowed');
        die('Only POST requests allowed');
    }

    // Token验证
    $token = $headers['X-Gitee-Token'] ?? '';
    if ($token === SECRET_KEY) {
        return true;
    }

    // 签名验证
    $timestamp = $headers['X-Gitee-Timestamp'] ?? '';
    $signature = $headers['X-Gitee-Signature'] ?? '';
    $expected = hash_hmac('sha256', $timestamp . "\n" . SECRET_KEY, SECRET_KEY);
    
    if ($signature !== $expected) {
        header('HTTP/1.1 403 Forbidden');
        die('Secret validation failed');
    }
    
    return true;
}

/**
 * 执行命令并记录日志
 */
function executeCommand($cmd) {
    $output = shell_exec($cmd . ' 2>&1');
    logMessage("执行命令: $cmd -> 输出: " . trim($output));
    return $output;
}

/**
 * 检查提交中是否包含目标目录的变化
 */
function hasTargetDirChanges($commits) {
    if (!CHECK_TARGET_CHANGES || empty(TARGET_DIR)) {
        logMessage("配置为检查所有变化或目标目录为空，继续部署");
        return true;
    }
    
    $target_prefix = TARGET_DIR . '/';
    foreach ($commits as $commit) {
        // 检查新增的文件
        foreach ($commit['added'] ?? [] as $file) {
            if (strpos($file, $target_prefix) === 0) {
                logMessage("检测到目标目录新增文件: $file");
                return true;
            }
        }
        
        // 检查修改的文件
        foreach ($commit['modified'] ?? [] as $file) {
            if (strpos($file, $target_prefix) === 0) {
                logMessage("检测到目标目录修改文件: $file");
                return true;
            }
        }
        
        // 检查删除的文件
        foreach ($commit['removed'] ?? [] as $file) {
            if (strpos($file, $target_prefix) === 0) {
                logMessage("检测到目标目录删除文件: $file");
                return true;
            }
        }
    }
    
    logMessage("未检测到目标目录 '" . TARGET_DIR . "' 的变化，跳过部署");
    return false;
}

/**
 * 获取需要保护的文件列表（包括保护目录内的所有文件）
 */
function getProtectedFiles() {
    $protected_files = ['.', '..'];
    
    // 添加保护目录及其内容
    foreach (PROTECTED_DIRS as $protected_dir) {
        $protected_files[] = $protected_dir;
        
        // 如果保护目录存在，递归获取目录内的所有文件和子目录
        $full_path = WEBSITE_ROOT . '/' . $protected_dir;
        if (is_dir($full_path)) {
            $iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($full_path, RecursiveDirectoryIterator::SKIP_DOTS),
                RecursiveIteratorIterator::SELF_FIRST
            );
            
            foreach ($iterator as $item) {
                if ($item->isDir()) {
                    $relative_path = str_replace(WEBSITE_ROOT . '/', '', $item->getPathname());
                    $protected_files[] = $relative_path;
                } else {
                    $relative_path = str_replace(WEBSITE_ROOT . '/', '', $item->getPathname());
                    $protected_files[] = $relative_path;
                }
            }
        }
    }
    
    logMessage("保护文件列表: " . implode(', ', array_slice($protected_files, 0, 10)) . (count($protected_files) > 10 ? '...' : ''));
    return $protected_files;
}

/**
 * 部署主流程
 */
function deploy() {
    $tempDir = WEBSITE_ROOT . '/.temp_deploy';
    
    // 1. 清理并创建临时目录
    if (is_dir($tempDir)) {
        executeCommand("rm -rf $tempDir");
    }
    mkdir($tempDir, 0755, true);

    // 2. 配置Git并克隆仓库
    executeCommand(GIT_PATH . " config --global http.sslVerify false");
    executeCommand(GIT_PATH . " config --global url.https://gitee.com/.insteadOf git@gitee.com:");
    
    $cloneCmd = "cd $tempDir && " . GIT_PATH . " clone -b " . BRANCH . " " . GIT_REMOTE . " .";
    if (executeCommand($cloneCmd) === null || !is_dir("$tempDir/.git")) {
        throw new Exception('仓库克隆失败');
    }

    // 3. 验证目标目录
    if (TARGET_DIR && !is_dir("$tempDir/" . TARGET_DIR)) {
        throw new Exception('目标目录不存在: ' . TARGET_DIR);
    }

    // 4. 清理网站目录（保护重要文件和目录）
    $protected_files = getProtectedFiles();
    $dir = opendir(WEBSITE_ROOT);
    
    while (($file = readdir($dir)) !== false) {
        if (!in_array($file, $protected_files)) {
            $path = WEBSITE_ROOT . '/' . $file;
            if (is_dir($path)) {
                executeCommand("rm -rf $path");
                logMessage("删除目录: $file");
            } else {
                executeCommand("rm -f $path");
                logMessage("删除文件: $file");
            }
        } else {
            logMessage("保护文件/目录: $file");
        }
    }
    closedir($dir);

    // 5. 复制文件并设置权限
    $source = TARGET_DIR ? "$tempDir/" . TARGET_DIR : $tempDir;

    // 使用rsync排除保护目录，确保不会覆盖重要文件
    $exclude_args = '';
    foreach (PROTECTED_DIRS as $dir) {
        $exclude_args .= " --exclude='$dir'";
    }

    executeCommand("rsync -av $exclude_args $source/* " . WEBSITE_ROOT . "/ 2>&1");
    executeCommand("chown -R www:www " . WEBSITE_ROOT);
    executeCommand("chmod 755 " . __FILE__);

    // 6. 清理临时目录
    executeCommand("rm -rf $tempDir");
}

// ==================== 主执行流程 ====================

logMessage("=== Webhook 请求开始 ===");

try {
    // 1. 获取并验证请求
    $headers = getHeaders();
    logMessage("请求头: " . json_encode($headers, JSON_UNESCAPED_UNICODE));

    validateRequest($headers);

    // 2. 处理事件类型
    $event = $headers['X-Gitee-Event'] ?? '';
    logMessage("事件类型: $event");

    // 3. Ping测试处理
    if (($headers['X-Gitee-Ping'] ?? '') === 'true') {
        header('Content-Type: application/json');
        echo json_encode([
            'status' => 'success',
            'message' => 'Webhook is working',
            'time' => date('Y-m-d H:i:s')
        ]);
        logMessage("Ping测试响应成功");
        exit;
    }

    // 4. Push事件处理
    if ($event === 'Push Hook') {
        // 验证目录存在性
        if (!is_dir(WEBSITE_ROOT)) {
            throw new Exception('网站根目录不存在: ' . WEBSITE_ROOT);
        }

        // 获取请求体并解析
        $payload = file_get_contents('php://input');
        $data = json_decode($payload, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception('解析JSON数据失败: ' . json_last_error_msg());
        }

        // 检查是否需要部署（根据目标目录变化）
        $commits = $data['commits'] ?? [];
        if (!hasTargetDirChanges($commits)) {
            header('Content-Type: application/json');
            echo json_encode([
                'status' => 'skip',
                'message' => '目标目录无变化，跳过部署',
                'time' => date('Y-m-d H:i:s')
            ]);
            logMessage("跳过部署：目标目录无变化");
            exit;
        }

        // 执行部署
        deploy();

        // 返回成功响应
        $response = [
            'status' => 'success',
            'message' => '自动部署完成',
            'time' => date('Y-m-d H:i:s')
        ];

        header('Content-Type: application/json');
        echo json_encode($response, JSON_UNESCAPED_UNICODE);
        logMessage("部署完成: " . json_encode($response, JSON_UNESCAPED_UNICODE));
    } else {
        throw new Exception('不支持的事件类型: ' . $event);
    }

} catch (Exception $e) {
    // 错误处理
    logMessage("错误: " . $e->getMessage());
    header('HTTP/1.1 500 Internal Server Error');
    header('Content-Type: application/json');
    echo json_encode([
        'status' => 'error',
        'message' => $e->getMessage()
    ]);
}

logMessage("=== Webhook 请求结束 ===\n");
?>